/*
 Feather INI Parser - 1.41
 You are free to use this however you wish.

 If you find a bug, please attept to debug the cause.
 Post your environment details and the cause or fix in the issues section of GitHub.

 Written by Turbine.

 Website:
 https://github.com/Turbine1991/feather-ini-parser
 http://code.google.com/p/feather-ini-parser/downloads

 Help:
 Bundled example & readme.
 http://code.google.com/p/feather-ini-parser/wiki/Tutorials
 */

#ifndef COMM_INI_H_
#define COMM_INI_H_

#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <map>
#include <string>

#include "assert/__assert.h"
#include "string_cast.h"

inline bool VerifyName(const std::string& _name) {
    if (_name.empty())
        return false;

    for (std::string::const_iterator it = _name.begin(); it != _name.end(); ++it) {
        if ('a' <= *it && *it <= 'z')
            continue;
        else if ('A' <= *it && *it <= 'Z')
            continue;
        else if ('0' <= *it && *it <= '9')
            continue;
        else if ('-' == *it)
            continue;
        else if ('_' == *it)
            continue;
        else if ('.' == *it)
            continue;
        else if (' ' == *it)
            continue;
        else
            return false;
    }

    return true;
}

class INI {
 public:
    typedef std::map<std::string, std::string> keys_t;
    typedef std::map<std::string, keys_t> sections_t;

    typedef keys_t::iterator keysit_t;
    typedef sections_t::iterator sectionsit_t;

    typedef std::pair<std::string, std::string> keyspair_t;
    typedef std::pair<std::string, keys_t> sectionspair_t;

 public:
    INI(const std::string& fileName, bool parse = true) : filename(fileName) {
        if (parse)
            Parse();
    }

    ~INI() {
        Clear();
    }

    bool Create(const std::string& section) {
        if (!VerifyName(section)) {
            ASSERT2(false, "---%s---", section.c_str());
            return false;
        }

        if (Select(section))
            return false;

        currentsection = section;
        sections[section];

        return true;
    }

    bool Select(const std::string& section) {
        if (!VerifyName(section)) {
            ASSERT2(false, "---%s---", section.c_str());
            return false;
        }

        sectionsit_t sectionsit = sections.find(section);

        if (sectionsit == sections.end())
            return false;

        currentsection = section;

        return true;
    }

    keys_t CurrentKeys() const {
        if (currentsection.empty()) {
            return {};
        }

        auto sectionsit = sections.find(currentsection);
        if (sectionsit == sections.end()) {
            return {};
        }

        return sectionsit->second;
    }

    const std::string& CurrentSection() const {
        return currentsection;
    }

    std::string FileName() const {
        return filename;
    }

    template <class V>
    bool Set(const std::string& key, const V& value) {
        return Set(currentsection, key, value);
    }

    template <class V>
    bool Set(const std::string& section, const std::string& key, const V& value) {
        if (!VerifyName(section)) {
            ASSERT2(false, "---%s---", section.c_str());
            return false;
        }

        if (!VerifyName(key)) {
            ASSERT2(false, "---%s---", key.c_str());
            return false;
        }

        std::string svalue = string_cast(value).str();

        if (BUFFER_SIZE < key.size() + svalue.size() + 1) {
            ASSERT2(false, "%lu", (long unsigned int)(key.size() + svalue.size()));
            return false;
        }

        sections[section][key] = svalue;
        return true;
    }

    const std::string& Get(const std::string& key, const std::string& def) {
        return Get(currentsection, key, def);
    }

    std::string Get(const std::string& key) {
        return Get(key, "");
    }

    const std::string& Get(const std::string& section, const std::string& key, const std::string& def) {
        if (!VerifyName(section)) {
            ASSERT2(false, "---%s---", section.c_str());
            return def;
        }

        if (!VerifyName(key)) {
            ASSERT2(false, "---%s---", key.c_str());
            return def;
        }

        keysit_t keys = sections[section].find(key);

        if (keys == sections[section].end())
            return def;

        return keys->second;
    }

    template <typename V>
    V Get(const std::string& key, const V& def);

    template <typename V>
    V Get(const std::string& section, const std::string& key, const V& def);

    bool Parse() {
        std::string cur;
        FILE* file = fopen(filename.c_str(), "rb");

        if (NULL == file)
            return false;

        fseek(file, 0, SEEK_END);
        int length = (int)ftell(file);
        fseek(file, 0, SEEK_SET);
        ASSERT(1 * 1024 * 1024 > length);

        while (true) {
            if (feof(file))
                break;

            char line[BUFFER_SIZE] = {0};

            if (NULL == fgets(line, BUFFER_SIZE, file) || ferror(file))
                break;

            size_t line_length = strnlen(line, BUFFER_SIZE);
            if ((line_length >= 2) && line[0] == '/' && line[1] == '/')
                continue;

            if (line[line_length - 1] == '\n') {
                line[line_length - 1] = '\0';
                line_length -= 1;
            }

            if (line[0] == '[') {
                cur = "";

                char ssection[512] = {0};
#ifdef WIN32
                int ret = _snscanf(line, 512, "[%[.0-9a-zA-Z_ -]]", ssection);
#else
                int ret = sscanf(line, "[%[.0-9a-zA-Z_ -]]", ssection);
#endif

                ssection[sizeof(ssection) - 1] = '\0';

                ASSERT(0 < ret);

                if (0 == ret)
                    continue;

                if (!VerifyName(ssection)) {
                    ASSERT2(false, "---%s---", ssection);
                    continue;
                }

                cur = ssection;
                sections[cur];
            } else {
                ASSERT(0 < cur.length());

                if (0 == cur.length()) {
                    ASSERT(false);
                    continue;
                }

                char* token = strchr(line, '=');

                if (NULL == token) {
                    continue;
                }

                *token = '\0';
                const char* skey = line;
                const char* svalue = token + 1;

                if (!VerifyName(skey)) {
                    ASSERT2(false, "---%s---", skey);
                    continue;
                }

                sections[cur][skey] = svalue;
            }
        }

        fclose(file);

        return true;
    }

    void Clear() {
        sections.clear();
        currentsection = "";
    }
    void ClearExceptSection(const std::string& _sectionName) {
        INI::sectionsit_t iter = sections.begin();
        for (; iter != sections.end();) {
            if (0 == iter->first.compare(_sectionName)) {
                ++iter;
            } else {
                sections.erase(iter++);
            }
        }
    }

    void ClearSelection() {
        if (currentsection.empty()) {
            return;
        }
        auto sec = sections.find(currentsection);
        if (sec != sections.end()) {
            sections.erase(sec);
        }
        currentsection = "";
    }

    void Save(const std::string& filename = "") {
        FILE* file = fopen(((filename == "") ? this->filename : filename).c_str(), "wb");

        if (NULL == file) {
            return;
        }

        for (INI::sectionsit_t i = sections.begin(); i != sections.end(); ++i) {
            if (i->second.size() == 0)
                continue;

            const std::string temp = makeSection(i->first);
            fwrite(temp.c_str(), 1, temp.length(), file);

            for (INI::keysit_t j = i->second.begin(); j != i->second.end(); ++j) {
                const std::string temp = makeKeyValue(j->first, j->second);
                fwrite(temp.c_str(), 1, temp.length(), file);
            }
        }

        fclose(file);
    }

    sections_t& Sections() {
        currentsection = "";
        return sections;
    }

    bool Empty() const {
        return sections.empty();
    }

 private:
    std::string makeSection(const std::string& section) {
        std::string line;
        line += '[';
        line += section;
        line += ']';
        line += '\n';

        return line;
    }

    std::string makeKeyValue(const std::string& key, const std::string& value) {
        std::string line;
        line += key;
        line += '=';
        line += value;
        line += '\n';

        return line;
    }

 private:
    static const unsigned int BUFFER_SIZE = 4096;

    std::string currentsection;
    sections_t sections;

    std::string filename;
};

template <typename V>
V INI::Get(const std::string& key, const V& def) {
    return Get(currentsection, key, def);
}

template <typename V>
V INI::Get(const std::string& section, const std::string& key, const V& def) {
    if (!VerifyName(section)) {
        ASSERT2(false, "---%s---", section.c_str());
        return def;
    }

    if (!VerifyName(key)) {
        ASSERT2(false, "---%s---", key.c_str());
        return def;
    }

    keysit_t keys = sections[section].find(key);

    if (keys == sections[section].end())
        return def;

    if (keys->second.empty())
        return def;

    // V result;

    number_cast<V> cast(keys->second.c_str());
    if (cast.valid())
        return cast;
    return def;
}

template <>
inline std::string INI::Get<std::string>(const std::string& section, const std::string& key, const std::string& def) {
    return Get(section, key, def);
}

#endif  // COMM_INI_H_
